import { formatFileSize as formatSize } from "../utils/tools";
import { readFile, stat } from "fs";
import { createHash } from "crypto";
import sharp from "sharp";
type ImgType = "png" | "jpeg" | "gif" | "webp";
interface ImgFile {
  size: string;
  url: string;
}
let hashConnect = "-";
let hashLength = 8;
let isNeedHash = true;
let assetsInlineLimit = 4096;
const imgFileMap: {
  [key: string]: ImgFile;
} = {};
const imageRegex = /\.(png|jpe?g|gif|webp)$/i;

function generateHash(content: string | Buffer) {
  return createHash("sha256").update(content).digest("hex");
}

function getFileExt(filePath: string) {
  return filePath.split(".").pop();
}

function getFileName(filePath: string) {
  return filePath.split("/").pop();
}

function generateHashFileName(
  filePath: string,
  isConnectHash: boolean,
  hashLen: number,
  hash: string
) {
  const fileName = getFileName(filePath) as string;
  const ext = getFileExt(fileName);
  return isConnectHash
    ? `${fileName.replace(`.${ext}`, "")}${hashConnect}${hash.slice(
        0,
        hashLen
      )}.${ext}`
    : `${fileName.replace(`.${ext}`, "")}.${ext}`;
}
function getFileSize(filePath: string): Promise<number | void> {
  return new Promise((resolve) => {
    stat(filePath, (err, stats) => {
      if (err) resolve(void 0);
      resolve(stats.size);
    });
  });
}

function readFileContent(filePath: string) {
  return new Promise((resolve) => {
    readFile(filePath, (err, data) => {
      if (err) resolve(void 0);
      resolve(data);
    });
  });
}

function formatterLog(filePath: string, sizeBefore: string, sizeAfter: string) {
  console.log(
    "\n\x1B[34m",
    `【${filePath}】压缩成功`,
    "\x1B[0m",
    "\x1B[31m",
    sizeBefore,
    "\x1B[0m",
    "===>",
    "\x1B[32m",
    sizeAfter,
    "\x1B[0m"
  );
}

function tinifyImg(imgFile: ImgFile): any {
  const { size, url: filePath } = imgFile;
  const ext: ImgType = getFileExt(filePath) as ImgType;
  return new Promise((resolve) => {
    switch (ext) {
      case "png":
        sharp(filePath, { limitInputPixels: false })
          .png({ quality: 10 })
          .toBuffer((err, data, info) => {
            if (err) {
              console.log(`\n\x1B[31m【${filePath}】压缩失败\x1B[0m`, err);
              resolve(void 0);
            } else {
              formatterLog(filePath, size, formatSize(info.size));
              resolve(data);
            }
          });
        break;
      case "jpeg":
        sharp(filePath, { limitInputPixels: false })
          .jpeg({ quality: 10 })
          .toBuffer((err, data, info) => {
            if (err) {
              console.log(`\n\x1B[31m【${filePath}】压缩失败\x1B[0m`, err);
              resolve(void 0);
            } else {
              formatterLog(filePath, size, formatSize(info.size));
              resolve(data);
            }
          });
        break;
      case "gif":
        sharp(filePath, { animated: true, limitInputPixels: false })
          .gif({ interFrameMaxError: 10, colours: 200 })
          .toBuffer((err, data, info) => {
            if (err) {
              console.log(`\n\x1B[31m【${filePath}】压缩失败\x1B[0m`, err);
              resolve(void 0);
            } else {
              formatterLog(filePath, size, formatSize(info.size));
              resolve(data);
            }
          });
        break;
      case "webp":
        sharp(filePath, { limitInputPixels: false })
          .webp({ quality: 10 })
          .toBuffer((err, data, info) => {
            if (err) {
              console.log(`\n\x1B[31m【${filePath}】压缩失败\x1B[0m`, err);
              resolve(void 0);
            } else {
              formatterLog(filePath, size, formatSize(info.size));
              resolve(data);
            }
          });
        break;
      default:
        resolve(void 0);
    }
  });
}
export default () => {
  return {
    name: "vite-plugin-tinify-image",
    enforce: "pre", // 在其他插件处理之前，先处理
    apply: "build", // 只在生产环境构建时启用
    async configResolved(config: any) {
      assetsInlineLimit = config.build.assetsInlineLimit;
      const assetFileNames =
        config?.build?.rollupOptions?.output?.assetFileNames;
      if (assetFileNames) {
        if (/\[hash:?(\d*)\]/.test(assetFileNames)) {
          hashConnect = "";
          assetFileNames.replace(
            /\[name\](.)\[hash:?(\d*)\]/,
            (match: string, $1: string, $2: string) => {
              hashConnect = $1;
              hashLength = Number($2) || 8;
              return "";
            }
          );
        } else {
          isNeedHash = false;
          hashConnect = "";
        }
      } else {
        hashConnect = "-";
      }
    },
    async load(id: string) {
      if (imageRegex.test(id)) {
        const size = await getFileSize(id);
        if (size && size > assetsInlineLimit) {
          const res: any = await readFileContent(id);
          if (res) {
            imgFileMap[
              generateHashFileName(
                id,
                isNeedHash,
                hashLength,
                generateHash(res)
              )
            ] = {
              size: formatSize(size),
              url: id,
            };
          }
        }
      }
    },
    async generateBundle(_options: any, bundle: any) {
      console.log("\n===============开始图片压缩==============");
      for (const key in bundle) {
        if (imageRegex.test(key)) {
          const fileName = getFileName(key);
          if (fileName && imgFileMap[fileName]) {
            const res = await tinifyImg(imgFileMap[fileName]);
            if (res) {
              bundle[key].source = res;
            }
          }
        }
      }
      console.log("\n===============完成图片压缩==============");
    },
  };
};
